﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;

using alib.Debugging;
using alib.Enumerable;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{le.Name} {rules.Length}")]
	public struct TriggerRuleGroup
	{
		public TriggerRuleGroup(LexicalEntry le, IEnumerable<TriggerRule> rules)
		{
			this.le = le;
			this.rules = rules as TriggerRule[] ?? rules.ToArray();
		}
		public readonly LexicalEntry le;
		public readonly TriggerRule[] rules;
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///  trigger rule groups
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class TriggerRuleMgr : identity_node, IReadOnlyList<TriggerRuleGroup>, ISupportInitializeNotification,
		schema.ρρEntryMgr
	{
		public TriggerRuleMgr(IRtParent prv)
			: base(prv)
		{
		}

		///////////////////////////////////////////////////////////////////////
		///
		EntryMgr em;
		public EntryMgr EntryMgr
		{
			get { return em; }
			set { em = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////

		MrsManager mrsm { get { return em.mrsm; } }

		public event EventHandler Initialized;

		public bool IsInitialized { get { return arr != null; } }

		public void BeginInit() { }

		public void EndInit()
		{
			FeatureInfo fi;
			var trcfg = em.AgreeConfig.TriggerRules;
			var feats = em.ftm.feat_map;

			ifeat_context = feats.TryGetValue(trcfg.ContextFeat, out fi) ? fi.i_feat : -1;
			ifeat_input = feats.TryGetValue(trcfg.InputFeat, out fi) ? fi.i_feat : -1;
			ifeat_output = feats.TryGetValue(trcfg.OutputFeat, out fi) ? fi.i_feat : -1;
			ifeat_filter = feats.TryGetValue(trcfg.FilterFeat, out fi) ? fi.i_feat : -1;

			path_flags_trigger = new FsPath(em.im, trcfg.TriggerPath);
			this.arr = em._trigger_rules
						.AsParallel()
						.Where(tr => tr._precache_mrses(this))
						.GroupBy(tr => tr.FlagsTrigger)
						.Select(grp => new TriggerRuleGroup(grp.Key, grp/*.OrderBy(_g => _g.Context.variables.Length)*/))
				//.Sort(_trigger_rule_order.Instance)
						.ToArray(em._trigger_rules.Count);
#if false
			for (int i = 0; i < arr.Length; i++)
				Debug.Print("{0} {1} rules", arr[i].le.Name, arr[i].rules.Length);
#endif
		}

		TriggerRuleGroup[] arr;

		public int ifeat_context;
		public int ifeat_input;
		public int ifeat_output;
		public int ifeat_filter;
		public FsPath path_flags_trigger;

		public IEnumerable<vacuous_lexent> CheckRules(GenerationInput input)
		{
			for (int i = 0; i < arr.Length; i++)
			{
				var trg = arr[i];
				if (trg.rules.Any(tr => IsSatisfiedBy(tr, input)))
				{
					//gctrl.bp("trigger rule fired", rule.Expanded);

					yield return new vacuous_lexent(input, trg.le, input.NoCoverage);
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool IsSatisfiedBy(TriggerRule tr, GenerationInput input)
		{
			/// not using PredRestrict; zero'd out the RegEx preds in the TFS instead
			return mrsm.RelMatchHelper.CompareRels(tr.Context, input.RelSlots, false, false);
		}

		public TriggerRuleGroup this[int index] { get { return arr[index]; } }

		public int Count { get { return arr.Length; } }

		public IEnumerator<TriggerRuleGroup> GetEnumerator() { return arr.Enumerator(); }

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }

		//sealed class _trigger_rule_order : IComparer<TriggerRuleGroup>
		//{
		//	public static IComparer<TriggerRuleGroup> Instance = new _trigger_rule_order();
		//	public int Compare(TriggerRuleGroup x, TriggerRuleGroup y)
		//	{
		//		int q = y.rules[y.rules.Length - 1].Context.variables.Length - x.rules[x.rules.Length - 1].Context.variables.Length;
		//		return q != 0 ? q : (q = x.rules.Length - y.rules.Length) != 0 ? q : x.rules[0]._id - y.rules[0]._id;
		//	}
		//};
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public sealed class TriggerRule : StaticExpandEntry
	{
		public TriggerRule(Grammar g, Type t, ConsDefs consdef)
			: base(g, t, consdef)
		{
		}
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		TfsSlot[] _context;//, _input, _output, _filter;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		LexicalEntry flags_trigger;

		public bool _precache_mrses(TriggerRuleMgr trm)
		{
			var tfs = Expanded;

			TfsSlot ts;
			if (!(ts = tfs[trm.path_flags_trigger]).IsValid)
				throw new Exception();

			Entry ent;
			String s_le = tu.GetStringValue(ts.FlagsId);
			if (s_le == null || !em.entry_dict.TryGetValue(s_le, out ent))
			{
				//Console.WriteLine("Trigger rule '{0}' references unknown lexical entry '{1}'. The rule will be ignored.", Name, s_le);
				return false;
			}
			flags_trigger = ent as LexicalEntry;
			if (flags_trigger == null)
			{
				//Console.WriteLine("Trigger rule '{0}' references entry '{1}', which is not a lexical entry. The rule will be ignored.", Name, s_le);
				return false;
			}

			ts = tfs[trm.ifeat_context];
			_context = ts.IsValid ? mrsm.GetRelSlots(ts) : null;

			if (_context != null)
			{
				tfs.trigger_rule_regexes = new Dictionary<int, Regex>();
				foreach (var rel in _context)
				{
					int ix1 = tfs.GetEdgeIndex(mrsm.ifeat_pred, rel.out_mark);
					if (ix1 <= 0)
						throw new Exception();
					var efpred = tfs.entries[ix1].e_FlagsId;

					String s_pred = tu.TypeNameOrStringValue(efpred);

					if (s_pred[0] == '~')
					{
						s_pred = s_pred.Substring(1);

						tfs.trigger_rule_regexes[rel.ix1] =
							new Regex(s_pred, RegexOptions.Compiled |
										RegexOptions.Singleline |
										RegexOptions.CultureInvariant |
										RegexOptions.IgnorePatternWhitespace);

						/// let's try this instead of PredRestrict in the unifier
						Debug.Assert(efpred >= 0);
						tfs.entries[ix1].e_FlagsId = 0;
					}
				}
			}
			return true;
		}

		public TfsSlot[] Context { get { return _context; } }
		public LexicalEntry FlagsTrigger { get { return flags_trigger; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public bool IsSatisfiedBy(GenerationInput input)
		{
			/// not using PredRestrict; zero'd out the RegEx preds in the TFS instead
			return input.RelMatchHelper.CompareRels(_context, input.RelSlots, false, false);
		}
	};

}